package code

import (
	"encoding/binary"
	"fmt"
	"strconv"
	"strings"
)

type Instructions []byte //生成的字节码指令们
type Opcode byte

const (
	OpPushConstantByIndex Opcode = iota //一个操作数，要push的值在常量池中的索引，此命令使虚拟机在常量池中找到常量并压栈
	OpPop                               //出栈
	OpAdd                               //将栈顶的两个数相加
	OpSub
	OpMul
	OpDiv
	OpTrue
	OpFalse
	OpEqual
	OpNotEqual
	OpGreaterThan
	OpMinus
	OpBang
	OpJumpNotTruthy
	OpJump
	OpNull
	OpGetGlobal //从栈顶弹出元素，保存在以操作数为索引的全局存储中
	OpSetGlobal //从全局存储中根据索引读取值并压栈
	OpArray     //从栈中取出N个元素，组成object.Array,并压栈
	OpHash      //从栈中取出M个元素(包括k和v的数量)，组成object.Hash，并压栈
	OpIndex
	OpCall        //执行栈顶的函数
	OpReturnValue //让虚拟机返回位于栈顶的一个值
	OpReturn      //回到调用函数之前的地方，并隐式返回Null
)

type Definition struct {
	Name          string
	OperandWidths []int
}

var definitions = map[Opcode]*Definition{ //仅用于编译时调试，不用于虚拟机运行，虚拟机应该自己知道该怎么做
	OpPushConstantByIndex: {"OpPushConstantByIndex", []int{2}},
	OpAdd:                 {"OpAdd", []int{}},
	OpPop:                 {"OpPop", []int{}},
	OpSub:                 {"OpSub", []int{}},
	OpMul:                 {"OpMul", []int{}},
	OpDiv:                 {"OpDiv", []int{}},
	OpTrue:                {"OpTrue", []int{}},
	OpFalse:               {"OpFalse", []int{}},
	OpEqual:               {"OpEqual", []int{}},
	OpNotEqual:            {"OpNotEqual", []int{}},
	OpGreaterThan:         {"OpGreaterThan", []int{}},
	OpMinus:               {"OpMinus", []int{}},
	OpBang:                {"OpBang", []int{}},
	OpJumpNotTruthy:       {"OpJumpNotTruthy", []int{2}},
	OpJump:                {"OpJump", []int{2}},
	OpNull:                {"OpNull", []int{}},
	OpGetGlobal:           {"OpGetGlobal", []int{2}},
	OpSetGlobal:           {"OpSetGlobal", []int{2}},
	OpArray:               {"OpArray", []int{2}},
	OpHash:                {"OpHash", []int{2}},
	OpIndex:               {"OpIndex", []int{}},
	OpCall:                {"OpCall", []int{}},
	OpReturnValue:         {"OpReturnValue", []int{}},
}

// Lookup 根据操作码返回其Definition，包括操作名字和操作数长度
func Lookup(op byte) (*Definition, error) {
	def, ok := definitions[Opcode(op)]
	if !ok {
		return nil, fmt.Errorf("opcode %d undegined", op)
	}
	return def, nil
}

func (ins Instructions) String() string {
	offset := 0
	strBuilder := strings.Builder{}
	for offset < len(ins) {
		def, err := Lookup(ins[offset])
		if err != nil {
			strBuilder.WriteString(fmt.Sprintf("Opcode %d not defined\n", ins[offset]))
		}
		strBuilder.WriteString(fmt.Sprintf("%04d %s", offset, def.Name))
		offset += 1
		opNums, opNumLen := ReadOperands(def, ins[offset:])
		for _, opNum := range opNums {
			strBuilder.WriteByte(' ')
			strBuilder.WriteString(strconv.Itoa(opNum))
		}
		strBuilder.WriteByte('\n')
		offset += opNumLen
	}
	return strBuilder.String()
}

// Make 传入操作码和操作数，返回字节码指令，即编译单条指令
func Make(op Opcode, operands ...int) []byte {
	def, ok := definitions[op]
	if !ok {
		return []byte{}
	}
	instructionLen := 1
	for _, w := range def.OperandWidths {
		instructionLen += w
	}
	instruction := make([]byte, instructionLen)
	instruction[0] = byte(op)
	offset := 1
	for i, o := range operands {
		width := def.OperandWidths[i] //若def.OperandWidths[i]未定义，会报panic
		switch width {
		case 2:
			binary.BigEndian.PutUint16(instruction[offset:], uint16(o))
		}
		offset += width
	}
	return instruction
}

// ReadOperands 根据传入的字节码指令和definition，读取其操作数，并返回读取的字节数
func ReadOperands(def *Definition, ins Instructions) ([]int, int) {
	operands := make([]int, len(def.OperandWidths))
	offset := 0
	for i, width := range def.OperandWidths {
		switch width {
		case 2:
			operands[i] = int(ReadUint16(ins[offset:]))
		}
		offset += width
	}
	return operands, offset
}

// ReadUint16 从传入的字符切片开头开始读取2字节并转为uint16
func ReadUint16(ins Instructions) uint16 {
	return binary.BigEndian.Uint16(ins)
}
